/**
 * This file is part of decent-cpp.
 *
 * decent-cpp is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * decent-cpp is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with decent-cpp.  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once
#include <bitset>
#include <nlohmann/json.hpp>
#include <nonstd/optional.hpp>
#include <unordered_map>
#include "crypto.h"
#include "dcstdint.h"

#define DB_FILE "decent.sqlite3"

namespace dc {
/// Permissions
#define DC_PERMS                                                           \
  DC_PERM(manageServer), DC_PERM(manageUsers), DC_PERM(manageRoles),       \
      DC_PERM(manageChannels), DC_PERM(managePins), DC_PERM(manageEmotes), \
      DC_PERM(readMessages), DC_PERM(sendMessages),                        \
      DC_PERM(sendSystemMessages), DC_PERM(uploadImages),                  \
      DC_PERM(allowNonUnique)
#define DC_PERM_NO 1
#define DC_PERM_NEUTRAL 0
#define DC_PERM_YES 2
/// The permissions enum
enum class Permission {
#define DC_PERM(x) x
  DC_PERMS,
#undef DC_PERM
  SIZE
};
#ifndef DC_DATA_EXPORT_PERMS
#undef DC_PERMS
#endif
/// Convert a permission to its string name
std::string const& permissionToString(Permission perm);
/// Get a permission from a string name
nonstd::optional<Permission> permissionFromString(std::string const& str);
/// Permission bitstring type
typedef std::bitset<static_cast<size_t>(Permission::SIZE) * 2> permissions_t;
/// Check if a permissions contains a specific permission
unsigned permissionsGet(permissions_t const& perms, Permission perm);
/// Set the value of a certain permission
/// (value = DC_PERM_YES, DC_PERM_NEUTRAL, or DC_PERM_YES)
void permissionsSet(permissions_t& perms, Permission perm, unsigned value);
/// Converts the given permissions into json
nlohmann::json permissionsToJson(permissions_t perms);

class Channel;
class Message;

/// Role model
class Role {
 private:
  int id;
  std::string name;
  permissions_t permissions;
  bool isDefault;

 public:
  Role(int id, std::string name, permissions_t permissions, bool isDefault);
  Role(const Role& other);

  /// Converts role to json representation
  nlohmann::json to_json() const;

  /// Gets the permissions for this role
  permissions_t getPermissions();

  /// Get the "name" of this role, eg either _everyone etc or the string of id
  std::string getName();

  /// Get the id of this role
  int getId();

  /// Edits this role. True on success
  bool edit(nonstd::optional<std::string> name,
            nonstd::optional<permissions_t> perms,
            nonstd::optional<bool> isDefault);

  /// Deletes this role. Do not use after calling this
  void delet();

  /// Calculates effective guest permissions
  static permissions_t effectiveGuestPermissions(
      nonstd::optional<Channel> channel);

  /// Initializes the db table
  static void initTable();
  /// Gets all roles in the server
  static std::vector<Role> getAllRoles();
  /// Gets a role by "name" (see getName())
  static nonstd::optional<Role> getRole(std::string const& name);
  /// Gets a role by id
  static nonstd::optional<Role> getRole(int id);
  /// Adds a new role, returns Role, or nullopt if name exists
  static nonstd::optional<Role> newRole(std::string name,
                                        permissions_t permissions,
                                        bool isDefault);
  /// The everyone role
  static const std::string EVERYONE;
  /// The user role
  static const std::string USER;
  /// The guest role
  static const std::string GUEST;
};

/// User model
class User {
  friend class Message;

 private:
  int id;
  std::string username;
  std::string avatarUrl;
  std::string flair;
  bool online;
  std::string email;

 public:
  User(int id, std::string username, std::string avatarUrl, std::string flair,
       bool online, std::string email);
  User(const User& other);

  /// Converts user to json
  nlohmann::json to_json(bool isSelf) const;

  /// Get user id
  int getId() const;

  /// Set online
  void setOnline(bool online);

  /// Get all roles for this user
  std::vector<Role> getRoles() const;

  /// Calculates effective permissions based on the context
  permissions_t effectivePermissions(nonstd::optional<Channel> channel);

  /// Initializes the db table
  static void initTable();
  /// Gets all the users in the db
  static std::vector<User> getAllUsers();
  /// Try to log in a user. Returns userid on success, -1 on failure
  static int loginUser(std::string username, std::string pwd);
  /// Get a user by id
  static nonstd::optional<User> getUser(int id);
  /// Register a user. Password must be at least 6 chars.
  static nonstd::optional<User> registerUser(std::string const& username,
                                             std::string const& pwd);
};

/// Channel model
class Channel {
  friend class Message;

 private:
  int id;
  std::string name;

 public:
  Channel(int id, std::string name);
  Channel(const Channel& other);

  /// Converts to json, optionally adding session-specific info
  nlohmann::json to_json(nonstd::optional<User> user);

  /// Edits the name. True on success
  bool edit(std::string name);

  /// Deletes this channel. Do not use after calling
  void delet();

  /// Gets channel-specific role permissions (roleId -> permissions)
  std::unordered_map<int, permissions_t> getRolePermissions();

  /// Gets permissions for a specific role
  permissions_t getRolePermissions(Role role);

  /// Sets channel-specific role permissions
  void setRolePermissions(std::unordered_map<int, permissions_t> permissions);

  /// Marks this channel read for the given user
  void markRead(User user);

  /// Initializes channel table
  static void initTable();

  /// Get all channels
  static std::vector<Channel> getAllChannels();

  /// Get a channel by ID
  static nonstd::optional<Channel> getChannel(int id);

  /// Creates a new channel, returns ID or -1 if failed
  static int newChannel(std::string name);
};

class Message {
 public:
  enum Type { USER, SYSTEM, SIZE };

 private:
  int id;
  int channelId;
  Type type;
  std::string text;
  int authorId;
  std::string authorUsername;
  std::string authorAvatarUrl;
  u64_t dateCreated;
  u64_t dateEdited;
  bool pinned;

 public:
  Message(int id, int channelId, Type type, std::string text, int authorId,
          std::string authorUsername, std::string authorAvatarUrl,
          u64_t dateCreated, u64_t dateEdited, bool pinned);
  Message(const Message& other);

  /// Converts to json
  nlohmann::json to_json();

  /// Gets the list of user IDs mentioned by this message
  std::vector<int> getMentionedUserIds();

  /// Gets the message id
  int getId();

  // Gets the associated channel
  Channel getChannel();

  /// Initializes table
  static void initTable();

  /// Type to string
  static std::string typeToString(Type type);
  /// String to type
  static nonstd::optional<Type> stringToType(std::string str);
  /// Get mentioned IDs from string
  static std::vector<int> parseMessageForMentions(std::string msg);

  /// Get all mentions for a user, max 50
  static std::vector<Message> getMentionsForUser(
      User user, int limit = 50, int skip = 0,
      nonstd::optional<User> requesting = nonstd::nullopt);

  /// Get messages in a channel, max 50
  static std::vector<Message> getMessagesInChannel(
      Channel channel, int limit = 50,
      nonstd::optional<int> beforeId = nonstd::nullopt,
      nonstd::optional<int> afterId = nonstd::nullopt);

  static nonstd::optional<Message> newMessage(Channel channel, Type type,
                                              std::string text, User author);

  /// Get message by id
  static nonstd::optional<Message> getMessage(int id);
};

/// Session model
class Session {
 private:
  uuid id;
  u64_t dateCreated;
  int user;

 public:
  Session(uuid id, u64_t dateCreated, int user);
  Session(const Session& other);

  /// Converts this session to json
  nlohmann::json to_json();

  /// Gets all sessions this user owns
  std::vector<Session> getUserSessions();

  /// Deletes this session
  void delet();

  /// Gets the associated user
  User getUser();

  /// Gets this session's uuid
  uuid getUuid() const;

  /// Initializes the db table
  static void initTable();
  /// Get a session by id, or nullopt if not present
  static nonstd::optional<Session> getSession(uuid id);
  /// Get all sessions for a user.
  static std::vector<Session> getSessions(int user);
  /// Generate a new session.
  static Session newSession(int user);
};

/// Opens sqlite database
void data_init();

/// Gets a setting
nlohmann::json data_setting_get(std::string const& name,
                                nlohmann::json const& defaultVal);

/// Gets all settings
nlohmann::json data_setting_all();

/// Updates a setting
void data_setting_set(std::string const& name, nlohmann::json const& value);

/// Closes sqlite database
void data_close();
}  // namespace dc